<!DOCTYPE html>
<html lang="zh">

<head>
  <meta charset="UTF-8">
  <title>JSON 格式化展示</title>
  <style>
    body {
      display: flex;
      height: 100vh;
      margin: 0;
      padding: 0 13px;
      font-family: sans-serif;
      background: #f9f9f9;
    }

    .input,
    .output {
      width: 50%;
      box-sizing: border-box;
      overflow: auto;
      display: flex;
      flex-direction: column;
    }

    .input textarea {
      width: 100%;
      height: 100%;
      box-sizing: border-box;
      resize: none;
      font-family: monospace;
      font-size: 14px;
      padding: 10px;
      border: 1px solid #ccc;
      border-radius: 4px;
      background: #fff;
    }

    textarea:focus {
      outline: none;
    }

    .json-tree {
      font-family: 'Courier New', Courier, monospace;
      /* line-height: 1.1; */
      padding: 10px;
      font-size: 14px;
    }

    .json-object,
    .json-array {
      display: none;
    }

    .json-array.active,
    .json-object.active {
      display: flex;
      flex-direction: column;
      margin-left: 20px;
    }

    /* 括号样式 */
    .bracket_left,
    .bracket_right {
      color: gray;
      left: -10px;
    }

    .bracket_left::before {
      cursor: pointer;
      content: "+";
      color: red;
    }

    .bracket_left.active::before {
      content: "-";
    }

    .json-object:first-child {
      margin-left: 0;
    }

    .json-key {
      color: purple;
      font-weight: bold
    }

    .json-value {
      color: green;
    }

    .json-value-number {
      color: blue;
    }

    .json-item {
      margin-left: 20px;
    }

    .json-item:not(:last-child) {
      margin-bottom: 5px;
    }

    .action-panel {
      padding: 5px 10px;
      display: flex;
      justify-content: flex-end;
    }

    .sep,
    .comma {
      color: gray;
    }

    .action-btn {
      /* 绿色背景 */
      background-color: #4CAF50;
      /* 白色文字 */
      color: white;
      /* 无边框 */
      border: none;
      /* 内边距 */
      padding: 6px 10px;
      /* 文字居中 */
      text-align: center;
      /* 无下划线 */
      text-decoration: none;
      /* 行内块元素 */
      display: inline-block;
      /* 字体大小 */
      font-size: 16px;
      /* 外边距 */
      margin: 4px 2px;
      /* 手型光标 */
      cursor: pointer;
      /* 圆角 */
      border-radius: 4px;
      /* 平滑过渡效果 */
      transition: background-color 0.3s, transform 0.2s;
    }

    .action-btn:hover {
      /* 更深的绿色 */
      background-color: #45a049;
    }

    .action-btn:active {
      /* 轻微缩小 */
      transform: scale(0.98);
    }
  </style>
</head>

<body>
  <div class="input">
    <div class="action-panel">
      <button class="action-btn" id="beautifyJson">美化</button> <!-- 新增的美化按钮 -->
      <button class="action-btn" id="minifyJson">压缩</button> <!-- 新增的压缩按钮 -->
    </div>
    <textarea placeholder="请输入 JSON 字符串..."></textarea>
  </div>
  <div class="output">
    <div class="action-panel">
      <button class="action-btn" id="toggleExpand">全展开/收起</button>
      <button class="action-btn" id="copyJson">复制</button>
    </div>
    <div class="json-tree" id="output"></div>
  </div>

  <script>
    let isExpanded = true; // 初始为展开状态
    const textarea = document.querySelector("textarea");
    const output = document.getElementById("output");

    // 添加事件监听器到“美化”按钮
    document.getElementById('beautifyJson').addEventListener('click', beautifyJson);

    // 添加事件监听器到“压缩”按钮
    document.getElementById('minifyJson').addEventListener('click', minifyJson);

    // 添加事件监听器到“全展开/收起”按钮
    document.getElementById('toggleExpand').addEventListener('click', toggleExpand);

    // 添加事件监听器到“复制”按钮
    document.getElementById('copyJson').addEventListener('click', copyJson);

    // 实时监听输入
    textarea.addEventListener("input", () => {
      formatJSON(textarea.value);
    });

    // let defValue = `{"q":"asd","ages":[{"json":12},{"name":["li","xuelin"],"age":18}]}`;
    // textarea.value = defValue;
    // formatJSON(defValue);

    //展开收起
    function toggleExpand() {
      isExpanded = !isExpanded; // 切换目标状态
      const elements = document.querySelectorAll('.bracket_left');
      elements.forEach(element => {
        const isActive = element.classList.contains('active');
        // 如果当前状态与目标状态不同，则模拟一次点击来切换
        if (isActive !== isExpanded) {
          // 手动触发切换逻辑，避免直接操作 DOM 导致 innerHTML 不更新
          element.click(); // 触发事件监听器中定义的行为
        }
      });
    }

    // 复制
    function copyJson() {
      const jsonString = JSON.stringify(JSON.parse(textarea.value), null, 4);
      navigator.clipboard.writeText(jsonString).then(() => {
        alert("JSON 已复制到剪贴板！");
      }).catch(err => {
        console.error('未能复制文本: ', err);
      });
    }

    //格式化JSON
    function formatJSON(jsonString) {
      try {
        const json = JSON.parse(jsonString);
        output.innerHTML = "";
        let pre = prefixBound(),
          suf = suffixBound();
        output.appendChild(pre);
        output.appendChild(renderObject(json));
        output.appendChild(suf);
      } catch (e) {
        output.innerHTML = `<span style="color:red;">${e.message}</span>`;
      }
    }

    //渲染对象
    function renderObject(value, level = 0) {
      if (typeof value === "object" && value !== null) {
        var jsonObj = document.createElement("div");
        jsonObj.classList.add("json-object");
        jsonObj.classList.add("active");
        if (Array.isArray(value)) {
          value.forEach((val, index) => {
            var arrayAalueItem = document.createElement("span");
            let isLast = index === value.length - 1;
            renderNodeValue({ container: arrayAalueItem, value: val, isLast: isLast });
            jsonObj.appendChild(arrayAalueItem);
          });
        }
        else if (typeof value === "object") {
          let keys = Object.keys(value);
          for (let i = 0; i < keys.length; i++) {
            var isLast = i === (keys.length - 1);
            let val = value[keys[i]];
            var keyValue = document.createElement("div");
            var keyEle = document.createElement("span");
            keyEle.className = "json-key";
            keyEle.innerHTML = `"${keys[i]}"`;
            keyValue.appendChild(keyEle);
            keyValue.appendChild(createPunctuation({ innerHTML: ": ", classList: ["sep"] }));
            renderNodeValue({ container: keyValue, value: val, level, isLast });
            jsonObj.appendChild(keyValue);
          }
        }
        return jsonObj;
      } else {
        let className = "json-value";
        if (typeof value === "string") className = "json-string";
        else if (typeof value === "boolean" || value === null) className = "json-value";
        else if (typeof value === "number") className = "json-value";
        const span = document.createElement("span");
        span.className = className;
        span.textContent = typeof value === "string" ? `"${value}"` : String(value);
        return span;
      }
    }

    //渲染对象值
    function renderNodeValue({ container, value, level, isLast = false }) {
      if (typeof value === "string") {
        var valueEle = document.createElement("span");
        valueEle.className = "json-value";
        valueEle.innerHTML = `"${value}"`;
        container.appendChild(valueEle);
      }
      else if (typeof value === "number") {
        var valueEle = document.createElement("span");
        valueEle.className = "json-value-number";
        valueEle.innerHTML = `${value}`;
        container.appendChild(valueEle);
      }
      else if (Array.isArray(value)) {
        container.appendChild(prefixBound({ innerHTML: "[", type: "array", length: value.length }));
        container.appendChild(renderObject(value, level + 1));
        container.appendChild(suffixBound({ innerHTML: "]" }));
      }
      else if (typeof value === "object") {
        container.appendChild(prefixBound());
        container.appendChild(renderObject(value, level + 1));
        container.appendChild(suffixBound());
      }
      else { //娄底 当字符串处理
        var valueEle = document.createElement("span");
        valueEle.className = "json-value";
        valueEle.innerHTML = `"${value}"`;
        container.appendChild(valueEle);
      }
      if (!isLast)
        container.appendChild(createPunctuation());
    }

    //创建符号（默认逗号“，”）
    function createPunctuation({ innerHTML = ",", classList = ["comma"] } = {}) {
      let b1 = document.createElement("span")
      classList.forEach(className => b1.classList.add(className));
      b1.innerHTML = innerHTML;
      return b1;
    }

    //创建前边界（默认"{"）
    function prefixBound({ innerHTML = "{", classList = ["bracket_left"], defActive = true, type = "object", length = 0 } = {}) {
      let b1 = document.createElement("span")
      classList.forEach(className => b1.classList.add(className));
      if (defActive) {
        b1.classList.add("active");
      }
      b1.innerHTML = innerHTML;
      b1.addEventListener("click", () => {
        const isActive = b1.classList.contains("active");
        if (isActive) {
          // 即将收起
          b1.classList.remove("active");
          b1.nextElementSibling.classList.remove("active");
          if (type === "array") {
            b1.innerHTML = `Array[${length}`;
          } else if (type === "object") {
            b1.innerHTML = "Object{...";
          }
        } else {
          // 即将展开
          b1.classList.add("active");
          b1.nextElementSibling.classList.add("active");
          b1.innerHTML = innerHTML; // 恢复原始符号
        }
      });
      return b1;
    }

    //创建后边界（默认"}"）
    function suffixBound({ innerHTML = "}", classList = ["bracket_right"] } = {}) {
      let b1 = document.createElement("span")
      classList.forEach(className => b1.classList.add(className));
      b1.innerHTML = innerHTML;
      return b1;
    }

    /**
     * 美化JSON字符串
     */
    function beautifyJson() {
      try {
        const parsedJson = JSON.parse(textarea.value);
        // 使用JSON.stringify格式化JSON字符串
        const prettyJsonString = JSON.stringify(parsedJson, null, 2);
        // 将格式化后的JSON放回textarea
        textarea.value = prettyJsonString;
      } catch (e) {
        alert("请输入有效的JSON字符串！");
      }
    }

    /**
    * 压缩JSON字符串
    */
    function minifyJson() {
      try {
        const parsedJson = JSON.parse(textarea.value);
        // 使用JSON.stringify压缩JSON字符串，不使用空格
        const minifiedJsonString = JSON.stringify(parsedJson);
        // 将压缩后的JSON放回textarea
        textarea.value = minifiedJsonString;
      } catch (e) {
        alert("请输入有效的JSON字符串！");
      }
    }
  </script>
</body>

</html>